/***************************************************************************
 *   Copyright (C) 2009-2010 by Lasse Liehu                                *
 *   lliehu@kolumbus.fi                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "win32loginworker.h"

#include <wchar.h>

#include <QUrl>

#include "../GoogleLoginData.h"
#include "../loginoperation.h"
#include "win_functions.h"
using notifier::logWin32ErrForDbg;

class Win32LoginWorkerPrivate
{
    public:
        Win32LoginWorkerPrivate(GoogleLoginData& loginData_)
                : loginData(loginData_), completed(false), netHandle(NULL),
                connHandle(NULL), reqHandle(NULL)
        {
        }
        GoogleLoginData& loginData;
        bool completed;
        HINTERNET netHandle;
        HINTERNET connHandle;
        HINTERNET reqHandle;
        QByteArray receivedData;
};

Win32LoginWorker::Win32LoginWorker(GoogleLoginData& loginData) :
        d(new Win32LoginWorkerPrivate(loginData))
{
    connect(this, SIGNAL(updateStatus(Login::Status)), this,
            SLOT(noticeStatusChange(Login::Status) ), Qt::DirectConnection);
}

Win32LoginWorker::~Win32LoginWorker()
{
}

void Win32LoginWorker::run()
{
    init();
    if ( ! d->completed) sendReq();
    if ( ! d->completed) queryResponseHeader();
    if ( ! d->completed) receiveResponseData();
    if ( ! d->completed) parseData();
    closeHandles();
}

void Win32LoginWorker::noticeStatusChange(Login::Status newStatus)
{
    if (newStatus == Login::Completed)
    {
        d->completed = true;
    }
}

void Win32LoginWorker::init()
{
    // Changing our status to "working/busy"
    emit updateStatus(Login::Initializing);

    // Opening the initial HINTERNET handle
    d->netHandle = InternetOpenW(L"Google Reader Notifier Qt",
                                 INTERNET_OPEN_TYPE_DIRECT, // TODO proxy support?
                                 NULL, NULL, 0);
    if ( ! d->netHandle)
    {
        logWin32ErrForDbg(GetLastError(), "Win32LoginWorker", "InternetOpenW");
        d->loginData.setSid("");
        emit setConclusion(Login::InternalError, "");
        emit updateStatus(Login::Completed);
        return;
    }

    // Connecting to Internet (actually the connection isn't established yet)
    d->connHandle = InternetConnectW(d->netHandle, L"www.google.com",
                                     INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL,
                                     INTERNET_SERVICE_HTTP, 0, 0);
    if ( ! d->connHandle)
    {
        logWin32ErrForDbg(GetLastError(), "Win32LoginWorker","InternetConnectW");
        d->loginData.setSid("");
        emit setConclusion(Login::InternalError, "");
        emit updateStatus(Login::Completed);
        return;
    }

    // Opening an HTTP(S) request
    d->reqHandle = HttpOpenRequestW(d->connHandle, L"POST",
                                    L"/accounts/ClientLogin", NULL,
                                    NULL, NULL, INTERNET_FLAG_NO_COOKIES |
                                    INTERNET_FLAG_NO_UI |
                                    INTERNET_FLAG_SECURE, 0);
    if ( ! d->reqHandle)
    {
        logWin32ErrForDbg(GetLastError(), "Win32LoginWorker","HttpOpenRequestW");
        d->loginData.setSid("");
        emit setConclusion(Login::UnknownError, "");
        emit updateStatus(Login::Completed);
        return;
    }
}

void Win32LoginWorker::sendReq()
{
    // Sending the request
    const wchar_t headers[] = L"Content-Type: application/x-www-form-urlencoded";
    QByteArray postData = "\nservice=reader&Email=";
    postData += QUrl::toPercentEncoding(d->loginData.email() );
    postData += "&Passwd=";
    postData += QUrl::toPercentEncoding(d->loginData.password() );
    if ( ! HttpSendRequestW(d->reqHandle, headers, wcslen(headers),
                            postData.data(), postData.size() ) )
    {
        DWORD err = GetLastError();
        logWin32ErrForDbg(err, "Win32LoginWorker", "HttpSendRequestW");
        d->loginData.setSid("");
        switch (err)
        {
            case ERROR_INTERNET_CANNOT_CONNECT:
            case ERROR_INTERNET_NO_DIRECT_ACCESS:
                emit setConclusion(Login::ConnectionError, tr("Couldn't "
                        "connect. Please check your network connection.") );
                break;
            case ERROR_INTERNET_NAME_NOT_RESOLVED:
                emit setConclusion(Login::ConnectionError, tr("Couldn't connect; "
                                   "couldn't resolve the server name. Please "
                                   "check your Internet connection.") );
                break;
            case ERROR_INTERNET_CONNECTION_ABORTED:
                emit setConclusion(Login::ConnectionError,
                        tr("Experienced problems when connecting. Please try again "
                        "and check your Internet connection and settings.") );
                break;
            case ERROR_INTERNET_CONNECTION_RESET:
                emit setConclusion(Login::ConnectionError,
                                   tr("Connection was reset.") );
                break;
            case ERROR_INTERNET_TIMEOUT:
                emit setConclusion(Login::ConnectionError,
                                   tr("Connection timed out.") );
                break;
            case ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR:
            case ERROR_INTERNET_HTTPS_TO_HTTP_ON_REDIR:
                emit setConclusion(Login::SthNeedsFixingError, "");
                break;
            default:
                emit setConclusion(Login::UnknownError, "");
        }
        emit updateStatus(Login::Completed);
        return;
    }
    emit updateStatus(Login::RequestSent);
}

void Win32LoginWorker::queryResponseHeader()
{
    // We've received it already, although we haven't read it yet
    emit updateStatus(Login::ResponseHeaderReceived);

    // Getting the status code
    DWORD statusCode = 0;
    DWORD bufferLength = sizeof(DWORD);
    DWORD headerIndex = 0;
    if ( ! HttpQueryInfo(d->reqHandle, HTTP_QUERY_STATUS_CODE |
                         HTTP_QUERY_FLAG_NUMBER, &statusCode, &bufferLength,
                         &headerIndex) )
    {
        logWin32ErrForDbg(GetLastError(), "Win32LoginWorker","HttpQueryInfo");
        d->loginData.setSid("");
        emit setConclusion(Login::UnknownError, "");
        emit updateStatus(Login::Completed);
        return;
    }

    // Examining the status code
    switch (statusCode)
    {
        case 200: // OK
            break;
        case 301: // moved
        case 302: // redirect
        case 303: // redirect method
        case 401: // denied
        case 404: // not found
        case 405: // bad method
            emit setConclusion(Login::SthNeedsFixingError, "");
            break;
        case 403: // forbidden
            emit setConclusion(Login::AuthenticationError, "");
            break;
        case 408: // timeout
            emit setConclusion(Login::ConnectionError, tr("Connection "
                               "timed out. Please try again later.") );
            break;
        case 500: // server error
            emit setConclusion(Login::UnknownServerError, "");
            break;
        default:
            qDebug("error: Win32LoginWorker: Received response %i", statusCode);
            emit setConclusion(Login::UnknownError,
                               tr("Couldn't handle server's response."));
    }
    if (statusCode != 200)
    {
        d->loginData.setSid("");
        emit updateStatus(Login::Completed);
        return;
    }
}

void Win32LoginWorker::receiveResponseData()
{
    // Reading the data from the server
    emit updateStatus(Login::ReceivingResponseData);
    const int BUFFER_SIZE = 50;
    char buffer[BUFFER_SIZE];
    DWORD bytesRead = 0;
    bool done = false;
    while (InternetReadFile(d->reqHandle, &buffer, BUFFER_SIZE, &bytesRead) )
    {
        if (bytesRead == 0)
        {
            done = true;
            break;
        }
        d->receivedData.append(buffer, bytesRead);
    }
    if (!done)
    {
        DWORD err = GetLastError();
        logWin32ErrForDbg(err, "Win32LoginWorker", "InternetReadFile");
        d->loginData.setSid("");
        switch (err)
        {
            case ERROR_INTERNET_CONNECTION_ABORTED:
                emit setConclusion(Login::ConnectionError,
                                   tr("Connection was terminated.") );
                break;
            case ERROR_INTERNET_CONNECTION_RESET:
                emit setConclusion(Login::ConnectionError,
                                   tr("Connection was reset. Please try again and "
                                   "check your Internet settings.") );
                break;
            case ERROR_INTERNET_TIMEOUT:
                emit setConclusion(Login::ConnectionError,
                                   tr("Connection timed out.") );
                break;
            default:
                emit setConclusion(Login::UnknownError, tr("Unknown "
                                   "error when reading data from the Internet.") );
        }
        emit updateStatus(Login::Completed);
        return;
    }
    emit updateStatus(Login::ResponseDataReceived);
}

void Win32LoginWorker::parseData()
{
    // Starting to parse the data we got
    emit updateStatus(Login::ParsingData);

    int sidPos = d->receivedData.indexOf("SID");
    if (sidPos == -1)
    {
        emit setConclusion(Login::NoSidInDataError, "");
        emit updateStatus(Login::Completed);
        return;
    }
    sidPos += 4;
    const char *data = d->receivedData.constData();
    QString SID;
    for (int i = sidPos; data[i] != '\n'; ++i)
    {
        SID += data[i];
    }
    d->loginData.setSid(SID);
    emit setConclusion(Login::Success, "");
    emit updateStatus(Login::Completed);
}

void Win32LoginWorker::closeHandles()
{
    InternetCloseHandle(d->reqHandle);
    d->reqHandle = NULL;
    InternetCloseHandle(d->connHandle);
    d->connHandle = NULL;
    InternetCloseHandle(d->netHandle);
    d->netHandle = NULL;
}
